package com.vmj.natives.java.lang;

import com.vmj.heap.*;
import com.vmj.heap.ClassLoader;
import com.vmj.heap.VMObject;
import com.vmj.instructions.base.MethodInvokeLogic;
import com.vmj.natives.NativeMethod;
import com.vmj.natives.Registry;
import com.vmj.rtda.Frame;
import com.vmj.rtda.LocalVars;
import com.vmj.rtda.OperandStack;
import com.vmj.rtda.Thread;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by yangshoujun on 2017/9/16 下午12:50.
 */
public class Class {

    public static final java.lang.String jlClass = "java/lang/Class";

    private static final java.lang.String _fieldConstructorDescriptor = "" +
            "(Ljava/lang/Class;" +
            "Ljava/lang/String;" +
            "Ljava/lang/Class;" +
            "II" +
            "Ljava/lang/String;" +
            "[B)V";

    private static final java.lang.String _constructorConstructorDescriptor = "" +
            "(Ljava/lang/Class;" +
            "[Ljava/lang/Class;" +
            "[Ljava/lang/Class;" +
            "II" +
            "Ljava/lang/String;" +
            "[B[B)V";

    public static void init() {
        Registry.register(jlClass, "getPrimitiveClass", "(Ljava/lang/String;)Ljava/lang/Class;", new NativeMethod() {
            public void execute(Frame frame) {
                VMObject nameObj = frame.getLocalVars().getRef(0);
                java.lang.String name = nameObj.getRefString();
                ClassLoader classLoader = frame.getMethod().getClazz().getClassLoader();
                VMObject clazzObj = classLoader.loadClass(name).getjClass();
                frame.getOperandStack().pushRef(clazzObj);
            }
        });

        Registry.register(jlClass, "getName0", "()Ljava/lang/String;", new NativeMethod() {
            public void execute(Frame frame) {
                VMObject _this = frame.getLocalVars().getThis();
                VMClass clazz = (VMClass) _this.getExtra();
                java.lang.String name = clazz.getJavaName();
                VMClass cls = frame.getMethod().getClazz();
                VMObject nameObj = StringPool.getStringObject(cls.getClassLoader(), name);
                frame.getOperandStack().pushRef(nameObj);
            }
        });

        Registry.register(jlClass, "isInterface", "()Z", new NativeMethod() {
            public void execute(Frame frame) {
                VMObject _this = frame.getLocalVars().getThis();
                VMClass clazz = (VMClass) _this.getExtra();
                frame.getOperandStack().pushInt(clazz.isInterface() ? 1 : 0);
            }
        });

        Registry.register(jlClass, "isPrimitive", "()Z", new NativeMethod() {
            public void execute(Frame frame) {
                VMObject _this = frame.getLocalVars().getThis();
                VMClass clazz = (VMClass) _this.getExtra();
                frame.getOperandStack().pushInt(clazz.isPrimitive() ? 1 : 0);
            }
        });

        Registry.register(jlClass, "desiredAssertionStatus0", "(Ljava/lang/Class;)Z", new NativeMethod() {
            public void execute(Frame frame) {
                frame.getOperandStack().pushBoolean(false);
            }
        });

        Registry.register(jlClass, "getModifiers", "()I", new NativeMethod() {
            public void execute(Frame frame) {
                LocalVars vars = frame.getLocalVars();
                VMObject _this = vars.getThis();
                VMClass clazz = (VMClass) _this.getExtra();
                int modifiers = clazz.getAccessFlags();

                frame.getOperandStack().pushInt(modifiers);
            }
        });

        Registry.register(jlClass, "getDeclaredConstructors0", "(Z)[Ljava/lang/reflect/Constructor;", new NativeMethod() {
            public void execute(Frame frame) {
                LocalVars vars = frame.getLocalVars();
                VMObject classObj = vars.getThis();
                boolean publicOnly = vars.getBoolean(1);
                VMClass clazz = (VMClass) classObj.getExtra();
                List<Method> constructors = clazz.getConstructor(publicOnly);
                int constructorCount = constructors.size();

                ClassLoader classLoader = frame.getMethod().getClazz().getClassLoader();
                VMClass constructorClass = classLoader.loadClass("java/lang/reflect/Constructor");
                VMObject constructorArr = VMObject.newArray(constructorClass.getArrayClass(), constructorCount);

                OperandStack stack = frame.getOperandStack();
                stack.pushRef(constructorArr);

                if(constructorCount > 0) {
                    Thread thread = frame.getThread();
                    VMObject[] constructorObjs = (VMObject[]) constructorArr.getData();
                    Method constructorInitMethod = constructorClass.getConstructor(_constructorConstructorDescriptor);
                    for(int i = 0; i < constructors.size();i++) {
                        Method constructor = constructors.get(i);
                        VMObject constructorObj = constructorClass.newObject();
                        constructorObj.setExtra(constructor);
                        constructorObjs[i] = constructorObj;

                        OperandStack ops = new OperandStack(9);
                        ops.pushRef(constructorObj);
                        ops.pushRef(classObj);
                        ops.pushRef(ClassUtils.toClassArr(classLoader, constructor.getParameterTypes()));
                        ops.pushRef(ClassUtils.toClassArr(classLoader, constructor.getExceptionTypes()));
                        ops.pushInt(constructor.getAccessFlags());
                        ops.pushInt(0);
                        ops.pushRef(ClassUtils.getSignatureStr(classLoader, constructor.getSignature()));
                        ops.pushRef(ClassUtils.toByteArr(classLoader, constructor.getAnnotationData()));
                        ops.pushRef(ClassUtils.toByteArr(classLoader, constructor.getParameterAnnotationData()));

                        Frame shimFrame = createShimFrame(thread, ops);
                        thread.pushFrame(shimFrame);
                        MethodInvokeLogic.invokeMethod(shimFrame, constructorInitMethod);
                    }
                }
            }
        });

        Registry.register(jlClass, "getSuperclass", "()Ljava/lang/Class;", new NativeMethod() {
            public void execute(Frame frame) {
                LocalVars vars = frame.getLocalVars();
                VMObject classObj = vars.getThis();
                VMClass extra = (VMClass) classObj.getExtra();
                VMClass superClass = extra.getSuperClass();
                if(superClass != null) {
                    frame.getOperandStack().pushRef(superClass.getjClass());
                } else {
                    frame.getOperandStack().pushRef(null);
                }
            }
        });

        Registry.register(jlClass, "forName0", "(Ljava/lang/String;ZLjava/lang/ClassLoader;Ljava/lang/Class;)Ljava/lang/Class;", new NativeMethod() {
            public void execute(Frame frame) {
                LocalVars vars = frame.getLocalVars();
                VMObject nameObj = vars.getRef(0);
                boolean initialize = vars.getBoolean(1);

                java.lang.String className = nameObj.getRefString();
                className = className.replaceAll("\\.", "/");
                VMClass clazz = frame.getMethod().getClazz().getClassLoader().loadClass(className);
                VMObject jClass = clazz.getjClass();
                if(initialize && !clazz.isInitStarted()) {
                    Thread thread = frame.getThread();
                    frame.setNextPc(thread.getPc());
                    VMClass.initClass(thread, clazz);
                } else {
                    frame.getOperandStack().pushRef(jClass);
                }
            }
        });

        Registry.register(jlClass, "getDeclaredFields0", "(Z)[Ljava/lang/reflect/Field;", new NativeMethod() {
            public void execute(Frame frame) {
                LocalVars vars = frame.getLocalVars();
                VMObject classObj = vars.getThis();
                boolean publicOnly = vars.getBoolean(1);

                VMClass extra = (VMClass) classObj.getExtra();
                List<Field> fields = extra.getField(publicOnly);
                int fieldCount = fields.size();
                ClassLoader classLoader = frame.getMethod().getClazz().getClassLoader();
                VMClass fieldClass = classLoader.loadClass("java/lang/reflect/Field");
                VMObject fieldArr = VMObject.newArray(fieldClass.getArrayClass(), fieldCount);

                OperandStack stack = frame.getOperandStack();
                stack.pushRef(fieldArr);

                if(fieldCount > 0) {
                    Thread thread = frame.getThread();
                    VMObject[] fieldObjs = (VMObject[]) fieldArr.getData();

                    Method fieldClassContructor = fieldClass.getConstructor(_fieldConstructorDescriptor);
                    for(int i = 0; i < fields.size();i++) {
                        Field field = fields.get(i);
                        VMObject fieldObj = fieldClass.newObject();
                        fieldObj.setExtra(field);
                        fieldObjs[i] = fieldObj;

                        OperandStack ops = new OperandStack(8);
                        ops.pushRef(fieldObj);
                        ops.pushRef(classObj);
                        ops.pushRef(StringPool.getStringObject(classLoader, field.getName()));

                        ops.pushRef(field.getType().getjClass());
                        ops.pushInt(field.getAccessFlags());
                        ops.pushInt(field.getSlotId());

                        ops.pushRef(ClassUtils.getSignatureStr(classLoader, field.getSignature()));
                        ops.pushRef(ClassUtils.toByteArr(classLoader, field.getAnnotationData()));

                        Frame shimFrame = createShimFrame(thread, ops);
                        thread.pushFrame(shimFrame);
                        MethodInvokeLogic.invokeMethod(shimFrame, fieldClassContructor);
                    }
                }
            }
        });

        Registry.register(jlClass, "getDeclaredMethods0", "(Z)[Ljava/lang/reflect/Method;", new NativeMethod() {
            public void execute(Frame frame) {
                VMObject classObj = frame.getLocalVars().getThis();
                Boolean isPublic = frame.getLocalVars().getBoolean(1);
                VMClass sourceClazz = (VMClass) classObj.getExtra();
                List<Method> methods = sourceClazz.getMethods();

                ClassLoader classLoader = frame.getMethod().getClazz().getClassLoader();
                VMClass jMethodClass = classLoader.loadClass("java/lang/reflect/Method");
                VMClass jClass = frame.getMethod().getClazz().getClassLoader().loadClass("java/lang/Class");
                List<VMObject> methodList = new ArrayList<VMObject>();
                for(int i = 0;i < methods.size();i++) {
                    Method method = methods.get(i);
                    if("<init>".equals(method.getName()) || "<clinit>".equals(method.getName())) {
                        continue;
                    }
                    if(method.isPublic() && !isPublic) {

                        VMObject methodObj = jMethodClass.newObject();
                        VMObject declaringClassObj = jClass.newObject();
                        declaringClassObj.setClazz(sourceClazz);
                        methodObj.setRefVar("clazz", "Ljava/lang/Class;", declaringClassObj);
                        methodObj.setRefVar("name", "Ljava/lang/String;", StringPool.getStringObject(classLoader, method.getName()));
                        // TODO set other attribute
                        methodList.add(methodObj);
                    }
                }

                VMObject[] objs = new VMObject[methodList.size()];
                methodList.toArray(objs);

                VMClass methodArrayClass = classLoader.loadClass("[Ljava/lang/reflect/Method;");
                VMObject arrObj = methodArrayClass.newObject();
                arrObj.setExtra(methodArrayClass);
                arrObj.setData(objs);
                frame.getOperandStack().pushRef(arrObj);
            }
        });

    }

    public static Frame createShimFrame(Thread thread, OperandStack stack) {
        Frame frame = new Frame(thread, ShimMethod.getShipReturnMethod());
        frame.setOperandStack(stack);
        return frame;
    }
}
